<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- 异步函数 -->
    <script>
        /* 基本用法 */
        function toUse() {
            // 期约写法
            let p = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'promise'); });
            p.then((args) => { console.log(args); });
            // 异步函数写法
            const asyncFun = async function () {
                let p = new Promise((resolve, reject) => { setTimeout(resolve, 2000, 'async'); });
                console.log(await p);
            };
            asyncFun();

            // 例 - 指定时间输出值（异步打印）
            const asyncPrint = async function (value, s) {
                await new Promise((resolve, reject) => { setTimeout(resolve, s * 1000); });
                console.log(value);
            };
            asyncPrint('test!', 3);

            // 理解await关键字
            function toAwait() {
                // eg.1
                const foo1 = async function () {
                    console.log(2);
                    await null;
                    console.log(4);
                }
                console.log(1);
                foo1();
                console.log(3);
                // 1 2 3 4

                // eg.2
                const foo2_1 = async function () {
                    console.log(2);
                    console.log(await Promise.resolve(8));
                    console.log(9);

                }
                const foo2_2 = async function () {
                    console.log(4);
                    console.log(await 6);
                    console.log(7);
                }
                console.log(1);
                foo2_1();
                console.log(3);
                foo2_2();
                console.log(5);
                // 1 2 3 4 5 8 9 6 7
            }
            setTimeout(toAwait, 4000);
        }
        // toUse();

        /* 语法 - 错误处理机制 */
        function toHandle() {
            // 返回Promise对象
            function test1() {
                const f1 = async function () { return 'hello world'; }
                f1().then(v => console.log(v)); // hello world
                // return语句返回的值, 是then方法回调函数的参数

                const f2 = async function () { throw new Error('error!'); };
                f2().then(res => console.log('resolve', res), rej => { console.log('reject', rej); });
                // async函数内部抛出错误, 返回的Promise对象变为reject状态, 抛出的错误对象作为catch方法回调函数的参数
            }
            setTimeout(test1, 5000);

            // Promise对象的状态变化
            function test2() {
                // async函数返回的 Promise 对象, 必须等到内部所有await命令后面的 Promise 对象执行完, 才会发生状态改变, 除非遇到return语句或者抛出错误。
                // 只有async函数内部的异步操作执行完, 才会执行then方法指定的回调函数
                async function getTitle(url) {
                    let response = await fetch(url);
                    let html = await response.text();
                    return html.match(/<title>([\s\S]+)<\/title>/i)[1];
                }
                // getTitle('http://tc39.github.io/ecma262/').then(console.log);
                // 抓取网页、取出文本、匹配页面标题. 只有这三个操作全部完成, 才会执行then方法里面的console.log
            }

            // await命令
            function test3() {
                // await后面通常是一个Promise对象, 返回该对象的结果, 不是则直接返回对应的值
                const f1 = async function () { return await Promise.resolve(123); };
                const f2 = async function () { return await [123]; }; // == return [123]
                f1().then(x => console.log(x)); // return后面的值是then回调函数的参数
                f2().then(x => console.log(x));

                // await后面是thenable对象时, await将其等同于Promise对象
                class Pause { // 写一个暂停类
                    constructor(ms) {
                        this.ms = ms; // 暂停的毫秒数
                    }
                    then(resolve, reject) { // 自定义then接口
                        const t0 = Date.now();
                        setTimeout(() => resolve(Date.now() - t0), this.ms);
                    }
                }
                const f3 = async function () {
                    const pauseTime = await new Pause(1000);
                    console.log(typeof pauseTime);
                    return await pauseTime; // 返回promise对象
                };
                f3().then(console.log);

                // await后面的Promise对象变为reject状态时, 后面函数中断执行, 参数被传入catch方法
                const f4 = async function () {
                    console.log(1);
                    await Promise.reject(3);
                    console.log(4);
                };
                f4().catch((x) => console.log(x));
                console.log(2); // 1 2 3
                // 想不中断异步操作, 使用try...catch/catch
                const f5 = async function () {
                    // 1
                    /* 
                        try {
                            await Promise.reject('f5 error1');
                        } catch (e) { console.log(e); }
                     */
                    // 2 (更简洁)
                    await Promise.reject('f5 erroe2').catch(e => console.log(e));
                    return await Promise.resolve('hello world');
                };
                f5().then(x => console.log(x));
            }
            setTimeout(test3, 6000);

            // 错误处理 - (相当于返回的promise对象reject)放在try..catch代码块中
            function test4() {
                const f = async function () {
                    try {
                        await new Promise((resolve, reject) => {
                            throw new Error('error!');
                        });
                    } catch (error) { }
                    return await 'hello world';
                };
                f().then(console.log, console.log);
            }
            setTimeout(test4, 7000);
        }
        // toHandle();
    </script>

    <!-- 使用注意点 -->
    <script>
        // 1 - 最好把await放在try...catch块中

        // 2 - 多个await命令后面的异步操作, 如果不存在继发关系, 最好同时触发
        function point2() {
            const getFoo = () => console.log('foo');
            const getBar = () => console.log('bar');
            // getFoo & getBar是两个独立的异步操作, 互不影响
            const test = async function () {
                // 被写成继发关系, 耗时
                let foo = await getFoo();
                let bar = await getBar();
                // 写法1
                let [foo1, bar1] = await Promise.all([getFoo(), getBar()]);
                // 写法2
                let fooPromise = getFoo();
                let barPromise = getBar();
                let foo2 = await fooPromise;
                let bar2 = await barPromise;
            };
            test();
        }
        // point2();

        // 3 - await只能用在async函数中, 用在普通函数中会报错

        // async函数可以保留运行堆栈
        {
            const a1 = () => {
                b().then(() => c());
            };
            const a2 = async () => {
                await b();
                c();
            };
            // a1内部运行异步任务b, b运行时a1不会中断, 继续执行, 等b运行结束后, b所在的上下文环境可能已经消失, b/c报错, 错误堆栈将不再包括a1
            // a2内部运行异步任务b, b运行时a2暂停执行, 上下文都保存, b/c报错, 错误堆栈将不再包括a1
        }
    </script>

    <!-- 异步函数策略 -->
    <script>
        /* 实现暂停sleep() */
        function tactic1() {
            async function sleep(delay) {
                return new Promise((resolve, reject) => setTimeout(resolve, delay));
            }
            async function sleepTest() {
                const t0 = Date.now();
                await sleep(50);
                console.log('sleepTime:', Date.now() - t0);
            }
            sleepTest();
        }
        // tactic1();

        /* 平行执行期约 */
        function tactic2() {
            async function randomDelay(id) {
                const delay = Math.random() * 1000; // 生成0~1000内的随机数
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log(`${id} finished`);
                        resolve(id);
                    }, delay)
                });
            }

            // 异步函数依次暂停, 等待每个超时的完成, 这样可以保证执行顺序, 但总执行时间会变长
            async function rdTest1() {
                const t0 = Date.now();
                for (let i = 0; i < 5; i++)
                    await randomDelay(i); // 循环执行五个期约
                console.log(`${Date.now() - t0}ms elapsed`); // 执行五个期约经过的总时间
            }
            // rdTest1();

            // 无需保证顺序时, 可以先一次性初始化所有期约, 再分别等待他们的结果
            async function rdTest2() {
                const t0 = Date.now();
                const promises = ['a', 'b', 'c', 'd', 'e'].map((x) => randomDelay(x));
                for (const p of promises)
                    console.log(`awaited ${await p}`); // 期约不按顺序执行, 但await按顺序收到每个期约的值
                console.log(`${Date.now() - t0}ms elapsed`);
            }
            rdTest2();

        }
        // tactic2();

        /* 串行执行期约 */
        function tactic3() {
            async function addTwo(x) { return x + 2; }
            async function addThree(x) { return x + 3; }
            async function addFive(x) { return x + 5; }

            async function addTen(x) {
                for (const fn of [addTwo, addThree, addFive]) {
                    x = await fn(x); // 结果反复套用
                }
                return x;
            }
            addTen(10).then(console.log);
        }
        // tactic3();
    </script>

    <!-- async函数底层原理 -->
    <script>
        function principle() {
            // async函数的实现原理, 就是将Generator函数和自动执行器, 包装在一个函数里
            const getData = () => new Promise((resolve, reject) => resolve('data'));
            // 同一个功能, async和generator的表达
            const asyncVer = async function () {
                const data = await getData();
                console.log('async data: ', data);
                const data2 = await getData();
                console.log('async data2: ', data2);
                return 'async success';
            };
            // asyncVer().then(x => console.log(x));
            const genVer = function* () {
                const data = yield getData();
                console.log('gen data: ', data);
                const data2 = yield getData();
                console.log('gen data2: ', data2);
                return 'generator success';
            };
            const g = genVer();
            let dataPromise = g.next();
            // 第一次调用next时, 停留在yield(286), data值不被确定, 下一次调用next时, data才会被确定(作为下一个yield的参数)
            // 但此时generator函数不会自动执行, 调用next方法会停留在下一个yield的位置， 所以需要编写自动执行的函数
            console.log(dataPromise instanceof Promise, dataPromise.value instanceof Promise);
            console.log(dataPromise.value);
            // 需要的data包含在PromiseResult中, 需要通过then方法执行取出
            dataPromise.value.then((value1) => {
                let data2Promise = g.next(value1); // value1为yield表达式后面getData()得到的值data
                data2Promise.value.then((value2) => {
                    g.next(value2); // 继续执行下一个yield
                });
            });

            // 根据以上方式, 写出一个通用函数
            // 思路: 编写myAsync, 接收一个generator函数, 返回promise对象
            const myAsync = function (generatorFn) {
                return function () {
                    const g = generatorFn.apply(this, arguments); // 调用生成器

                    // 返回promise对象, 可以用then方法直接使用
                    return new Promise((resolve, reject) => {

                        // 内部定义step函数, 用于模仿yield执行
                        // key有next和throw两种取值, 对应g的next和throw方法
                        // arg参数是保留promise.resolve的值传给下一个yield
                        function step(key, arg) {
                            let generatorResult;

                            // try...catch解决抛出错误
                            try {
                                // = g.next(next参数)
                                generatorResult = g[key](arg);
                            } catch (error) {
                                return reject(error);
                            }

                            // g.next()得到的结果是{value: Promise, done: false/true}
                            // value中存着promise, done中保存着next执行的情况, 最后一次为true
                            const { value, done } = generatorResult;

                            if (done) { // done为true
                                return resolve(value); // 把完成的promise结果resolve
                            } else {
                                // 未完成时, 每次调用g.next(), 调用then方法
                                return Promise.resolve(value).then(
                                    // onResolve方法, done不为true时, 持续调用next()
                                    (value) => { step('next', value) },
                                    // onReject方法, 如果Promise被reject, 错误被catch抛出
                                    (error) => { step('throw', error) }
                                );
                            }

                        }
                        // 执行next方法
                        step('next');
                    });
                }
            };

            let test = myAsync(genVer);
            test().then(x => console.log(x));
        }
        // principle();
    </script>

    <!-- 手写题 -->
    <script>
        // 1
        function Q1() {
            // 事件循环，将async await改写为promise形式，再判断输出顺序
            /*        改写下列代码       
                async function async1() {
                    console.log('async1 start')
                    await async2()
                    console.log('async1 end')
                }
                async function async2() {
                    console.log('async2')
                }
             */
            // ----- 改写
            const async1 = function () {
                return new Promise((resolve, reject) => {
                    console.log('async1 start');
                    async2();
                    resolve();
                }).then(() => { console.log('async end'); }); // then方法后的函数在同步线程代码执行完毕后执行
            }
            const async2 = function () {
                return new Promise((resolve, reject) => {
                    console.log('async2');
                    resolve();
                });
            }
            // -----
            console.log('script start')
            setTimeout(function () {
                console.log('setTimeOut')
            }, 0)
            async1()
            new Promise(function (resolve) {
                console.log('promise1')
                resolve()
            }).then(function () {
                console.log('promise2')
            })
            console.log('script end')

            // 输出顺序 script start, async1 start, async2, promise1, script end, async1 end, promise2, setTimeOut
        }
        // Q1();
    </script>
</body>

</html>